package com.snail.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONUtil;
import com.snail.config.ThirdPartyConfig;
import com.snail.exception.BizException;
//import com.snail.model.dto.user.BindAndLoginDTO;
import com.snail.model.dto.user.BindAndLoginDTO;
import com.snail.model.pojo.user.SysUser;
import com.snail.model.pojo.user.SysUserThird;
import com.snail.model.vo.user.LoginUser;
import com.snail.model.vo.user.LoginVO;
import com.snail.model.vo.user.ThirdPartyUserInfoVO;
import com.snail.result.ResultCode;
import com.snail.service.*;
import com.snail.utils.UserValidator;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import static com.snail.result.Result.failed;

/**
 * @Author 蔡徐坤
 * @Date 2025/8/15 17:30
 * @Description: TODO
 */
/**
 * 第三方免密登录 Service 实现
 */

@Service
@RequiredArgsConstructor
@Slf4j
public class ThirdPartyAuthServiceImpl implements ThirdPartyAuthService {

    // 注入一个Map，Spring会自动将所有ThirdPartyProviderService类型的Bean放进来
    // key: bean的名字, value: 实现类
    private final Map<String, ThirdPartyProviderService> providerServiceMap;

    private final SysUserThirdService sysUserThirdService;
    private final SysUserService sysUserService;
    private final SysUserRoleService sysUserRoleService;
    private final RedisTemplate<String,Object> redisTemplate;
    private final SysUserTagService userTagService;

    /**
     *  第三方免密登录
     * @param provider   第三方平台名称
     * @param code       第三方平台授权码
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public LoginVO loginByThird(String provider, String code) {
        String failKey  = "THIRD_LOGIN_FAIL:"  + provider + ":" + code;
        String blackKey = "THIRD_LOGIN_BLACK:" + provider + ":" + code;

        // 黑名单限制
        if (Boolean.TRUE.equals(redisTemplate.hasKey(blackKey))) {
            throw new BizException("登录次数过多，请稍后再试");
        }

        // 1) 再次向第三方换取用户信息（防止前端伪造）
        ThirdPartyProviderService providerService = getProviderService(provider); // 获取策略
        ThirdPartyUserInfoVO info = providerService.getUserInfoByCode(provider, code); // 调用策略方法
        if (info == null || info.getOpenId() == null) {
            incrementFailCount(failKey, blackKey);
            throw new BizException("获取第三方用户信息失败");
        }

        // 2) 查绑定
        SysUserThird bind = sysUserThirdService.findByProviderAndOpenId(provider, info.getOpenId());
        if (bind == null) {
            // 未绑定：提示前端去调用 /oauth2/bind
            incrementFailCount(failKey, blackKey);
            throw new BizException("UNBOUND_THIRD_PARTY");
        }

        SysUser localUser = sysUserService.getById(bind.getUserId());
        if (localUser == null) {
            incrementFailCount(failKey, blackKey);
            throw new BizException("绑定的本地用户不存在");
        }

        // 3) Sa-Token 登录
        doSaLogin(localUser.getUserId());

        // 成功清理计数
        redisTemplate.delete(failKey);
        redisTemplate.delete(blackKey);

        return buildLoginVO(localUser.getUserId(), localUser.getNickname());
    }


    /**
     * 第三方绑定并登录
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public LoginVO bindAndLogin(BindAndLoginDTO bindDTO) {
       String provider = bindDTO.getProvider();
       String code = bindDTO.getCode();
        String failKey  = "THIRD_BIND_FAIL:"  + provider + ":" + code;
        String blackKey = "THIRD_BIND_BLACK:" + provider + ":" + code;

        if (Boolean.TRUE.equals(redisTemplate.hasKey(blackKey))) {
            throw new BizException( "操作过于频繁，请稍后再试");
        }

        // 1) 再次向第三方换取用户信息（防止前端伪造）
        ThirdPartyProviderService providerService = getProviderService(provider); // 获取策略
        ThirdPartyUserInfoVO info = providerService.getUserInfoByCode(provider, code); // 调用策略方法

        if (info == null || info.getOpenId() == null) {
            incrementFailCount(failKey, blackKey);
            throw new BizException("获取第三方用户信息失败");
        }

        // 2) 若已绑定则直接登录
        SysUserThird exist = sysUserThirdService.findByProviderAndOpenId(provider, info.getOpenId());
        if (exist != null) {
            SysUser existedUser = sysUserService.getById(exist.getUserId());
            if (existedUser == null) {
                incrementFailCount(failKey, blackKey);
                throw new BizException("绑定关系异常，用户不存在");
            }
            doSaLogin(existedUser.getUserId());
            redisTemplate.delete(failKey);
            redisTemplate.delete(blackKey);
            return buildLoginVO(existedUser.getUserId(), existedUser.getUsername());
        }

        // 3) 校验 provider 是否在白名单
        String roleCode = ThirdPartyConfig.PROVIDER_TO_ROLE.get(provider);
        if (roleCode == null) {
            incrementFailCount(failKey, blackKey);
            throw new BizException( "该第三方暂不支持登录");
        }

        // 4) 注册本地用户（短昵称 + 默认头像）
        SysUser user = registerLocalUser(provider, info, bindDTO.getPhone(), bindDTO.getNickname());

        // 5) 绑定第三方
        sysUserThirdService.bindThirdParty(user.getUserId(), provider, info.getOpenId(), JSONUtil.toJsonStr(info));

        // 6) 分配默认角色
        List<Long> roleIds = sysUserRoleService.getRoleIdsByCodes(List.of(roleCode));
        if (roleIds != null && !roleIds.isEmpty()) {
            sysUserRoleService.addUserToRole(user.getUserId(), roleIds);
        }

        // 7) 登录
        doSaLogin(user.getUserId());

        redisTemplate.delete(failKey);
        redisTemplate.delete(blackKey);

        return buildLoginVO(user.getUserId(), user.getNickname());
    }


    /**
     * 登录成功后，构建登录返回值
     */
    private void doSaLogin(Long userId) {
        LoginUser loginUser = sysUserService.getLoginUserById(userId);
        String userIdStr = JSONUtil.toJsonStr(loginUser);
        StpUtil.login(userIdStr); // 建议用 userId 作为登录标识
        StpUtil.getSession().set("userInfo", JSONUtil.toJsonStr(loginUser));

    }

    private LoginVO buildLoginVO(Long userId, String username) {
        LoginVO vo = new LoginVO();
        vo.setAccessToken(StpUtil.getTokenValue());
        vo.setUserId(userId);
        vo.setUsername(username);
        vo.setRoleCodes(sysUserRoleService.getRoleCodesByUserId(userId));
        boolean hasTag = checkUserHasTag(userId);
        vo.setHasTag(hasTag);
        return vo;
    }

    /**
     * 检查用户是否有标签
     */
    private boolean checkUserHasTag(Long userId) {
        return userTagService.countByUserId(userId);

    }

    /**
     * 登录失败次数计数, 黑名单限制
     */
    private void incrementFailCount(String failKey, String blackKey) {
        Long count = redisTemplate.opsForValue().increment(failKey);
        redisTemplate.expire(failKey, 10, TimeUnit.MINUTES);
        if (count != null && count >= 5) {
            redisTemplate.opsForValue().set(blackKey, "1", 30, TimeUnit.MINUTES);
        }
    }

    /**
     * 根据provider名称获取对应的服务策略
     * @param provider 提供商名称（如 'weixin-mini', 'douyin'）
     * @return 对应的ThirdPartyProviderService实现
     */
    private ThirdPartyProviderService getProviderService(String provider) {
        // 定义一个映射关系：配置中的provider名称 -> 实际要用的Bean名称
        // 这个映射可以放在配置类里，这里为了清晰直接写在方法里
        String beanName;
        switch (provider) {
            case "weixin-mini":
                beanName = "weixinMiniProviderService"; // 对应微信小程序实现的Bean名
                break;
            case "douyin":
            case "wechat":
            case "merchant_platform":
            default:
                beanName = "ThirdPartyProviderService"; // 其他所有provider都用模拟实现
                break;
        }

        ThirdPartyProviderService service = providerServiceMap.get(beanName);
        if (service == null) {
            log.error("未找到对应的第三方登录服务实现 Bean: {}", beanName);
            throw new BizException("不支持的登录方式");
        }
        return service;
    }


    /**
     * 注册本地用户
     */
    private SysUser registerLocalUser(String provider, ThirdPartyUserInfoVO info, String phone, String nickname) {
        // username 用短前缀 + 随机串，避免过长和重复
        String username = provider + "_" + RandomUtil.randomString(6);

        SysUser user = new SysUser();
        user.setUsername(username);  // 系统内部唯一账号
        user.setNickname(nickname);  // 展示给前端的昵称
        user.setFilePath(info.getAvatar() != null ? info.getAvatar() : "http://172.172.2.99:9000/pic/2025/08/19/ab115febdd2f06da6308e8bddfb01edf.jpg");
        user.setDeleted(0);
        user.setPhone(phone);
        user.setForcePasswordChange(false);
        // 随机密码（MD5 或你现有加密方式）
        user.setPassword(DigestUtil.md5Hex(RandomUtil.randomString(16)));

        try {
            sysUserService.save(user);
        } catch (DuplicateKeyException e) {
            // 极小概率重复，追加后缀再保存一次
            user.setUsername(username + "_" + RandomUtil.randomString(3));
            sysUserService.save(user);
        }
        return user;
    }
}